/*
 * This is a common dao with basic CRUD operations and is not limited to any
 * persistent layer implementation
 *
 * Copyright (C) 2008  Imran M Yousuf (imyousuf@smartitengineering.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.smartitengineering.exim;

import com.smartitengineering.util.simple.IOFactory;
import com.smartitengineering.util.simple.reflection.ClassInstanceVisitorImpl;
import com.smartitengineering.util.simple.reflection.ClassScanner;
import com.smartitengineering.util.simple.reflection.Config;
import com.smartitengineering.util.simple.reflection.VisitCallback;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

/**
 * A central registrar for configuration scanners. It will register scanners
 * according their priority first and then in lexical order by class name. And
 * it will also iterate in the same order.
 * @author imyousuf
 * @since 0.4
 */
public final class ConfigRegistrar {

		private static final Set<ConfigScannerRegistryItem<ClassConfigScanner>> CLASS_CONFIG_SCANNER =
																																						Collections.synchronizedSortedSet(
						new TreeSet<ConfigScannerRegistryItem<ClassConfigScanner>>());
		private static final Set<ConfigScannerRegistryItem<PackageConfigScanner>> PACKAGE_CONFIG_SCANNER =
																																							Collections.synchronizedSortedSet(
						new TreeSet<ConfigScannerRegistryItem<PackageConfigScanner>>());

		static Set<ConfigScannerRegistryItem<ClassConfigScanner>> getClassConfigScannerRegistryItems() {
				return CLASS_CONFIG_SCANNER;
		}

		static Set<ConfigScannerRegistryItem<PackageConfigScanner>> getPackageConfigScannerRegistryItems() {
				return PACKAGE_CONFIG_SCANNER;
		}

		/**
		 * The following static block loads the default config scanner impl classes,
		 * so that if they have any static initializers then they can invoked
		 */
		static {
				String packageName =
							 ConfigRegistrar.class.getPackage().getName().concat(".impl");
				ClassScanner scanner = IOFactory.getDefaultClassScanner();
				scanner.scan(new String[] {packageName},
								new ClassInstanceVisitorImpl(IOFactory.getClassNameForVisitor(
								ClassConfigScanner.class), new VisitCallback<Config>() {

						public void handle(Config config) {
								try {
										IOFactory.getClassFromVisitorName(config.getClassName());
								}
								catch (Exception ex) {
										ex.printStackTrace();
								}
						}
				}));
				scanner.scan(new String[] {packageName},
								new ClassInstanceVisitorImpl(IOFactory.getClassNameForVisitor(
								PackageConfigScanner.class), new VisitCallback<Config>() {

						public void handle(Config config) {
								try {
										IOFactory.getClassFromVisitorName(config.getClassName());
								}
								catch (Exception ex) {
										ex.printStackTrace();
								}
						}
				}));
		}

		private ConfigRegistrar() {
				throw new AssertionError();
		}

		/**
		 * Register the class scanner with its priority
		 * @param classScannerClass Class scanner
		 * @param priority Priority of the scanner
		 * @throws IllegalArgumentException If class scanner is null
		 */
		public static void registerClassScanner(
						final Class<? extends ClassConfigScanner> classScannerClass,
						int priority) {
				CLASS_CONFIG_SCANNER.add(new ConfigScannerRegistryItem<ClassConfigScanner>(
								classScannerClass, priority));
		}

		/**
		 * Register the package scanner with its priority
		 * @param packageScannerClass Package scanner
		 * @param priority Priority of the scanner
		 * @throws IllegalArgumentException If class scanner is null
		 */
		public static void registerPackageScanner(
						final Class<? extends PackageConfigScanner> packageScannerClass,
						int priority) {
				PACKAGE_CONFIG_SCANNER.add(new ConfigScannerRegistryItem<PackageConfigScanner>(
								packageScannerClass, priority));
		}

		/**
		 * Scans (if required) and return the configuration for the resource class
		 * @param resourceClass Resource class
		 * @return config for the resource class, null if not config found for the
		 *          class.
		 */
		public static EximResourceConfig getConfigForClass(final Class resourceClass) {
				for (ConfigScannerRegistryItem<ClassConfigScanner> scanner :
						 CLASS_CONFIG_SCANNER) {
						EximResourceConfig config = scanner.getInstance().
										getResourceConfigForClass(resourceClass);
						if (config != null) {
								return config;
						}
				}
				return null;
		}

		/**
		 * Simply scan a package to gather all its configurations. This would be
		 * helpful if an only if the scanner caches the config result.
		 * @param resourcePackage Package to scan for resources
		 */
		public static void scanPackage(final Package resourcePackage) {
				for (ConfigScannerRegistryItem<PackageConfigScanner> scanner :
						 PACKAGE_CONFIG_SCANNER) {
						scanner.getInstance().scanPackageForResourceConfigs(resourcePackage);
				}
		}

		/**
		 * Return all configurations from a package and in process if requried also
		 * initiate a scan process
		 * @param resourcePackage Package to scan
		 * @return All configurations from the resource package
		 */
		public static Collection<EximResourceConfig> getConfigForPackage(
						final Package resourcePackage) {
				Set<EximResourceConfig> configs = new HashSet<EximResourceConfig>();
				for (ConfigScannerRegistryItem<PackageConfigScanner> scanner :
						 PACKAGE_CONFIG_SCANNER) {
						PackageConfigScanner configScanner = scanner.getInstance();
						configScanner.scanPackageForResourceConfigs(resourcePackage);
						Collection<Class> configClasses = configScanner.
										getConfiguredResourceClasses();
						for (Class configClass : configClasses) {
								if (resourcePackage.equals(configClass.getPackage())) {
										configs.add(configScanner.getConfigurations().get(
														configClass));
								}
						}
				}
				return configs;
		}

		public static class ConfigScannerRegistryItem<T extends ConfigScanner>
						implements Comparable<ConfigScannerRegistryItem<T>> {

				private Class<? extends T> configClass;
				private int priority;

				public ConfigScannerRegistryItem(Class<? extends T> configClass,
																				 int priority) {
						if (configClass == null) {
								throw new IllegalArgumentException();
						}
						this.configClass = configClass;
						this.priority = priority;
				}

				@Override
				public boolean equals(Object obj) {
						if (obj == null) {
								return false;
						}
						if (getClass() != obj.getClass()) {
								return false;
						}
						final ConfigScannerRegistryItem<T> other =
																							 (ConfigScannerRegistryItem<T>) obj;
						if (this.configClass != other.configClass &&
								(this.configClass == null ||
								 !this.configClass.equals(other.configClass))) {
								return false;
						}
						if (this.priority != other.priority) {
								return false;
						}
						return true;
				}

				@Override
				public int hashCode() {
						int hash = 7;
						hash =
						71 * hash +
						(this.configClass != null ? this.configClass.hashCode() : 0);
						hash = 71 * hash + this.priority;
						return hash;
				}

				public int compareTo(ConfigScannerRegistryItem<T> obj) {
						if (obj == null) {
								return 1;
						}
						Integer myPriority = Integer.valueOf(priority);
						Integer otherPriority = Integer.valueOf(obj.priority);
						int myComp = myPriority.compareTo(otherPriority);

						if (myComp == 0) {
								int classComp = configClass.getName().compareTo(obj.configClass.
												getName());
								return classComp;
						}
						else {
								return myComp;
						}
				}

				/**
				 * Returns the instance of the scanner.
				 * @return If static getInstance() method is implemented then it will
				 *          invoke that first else will try to use no-args constructor.
				 *          Null if both fails.
				 */
				public T getInstance() {
						T instance = null;
						try {
								Method getInstanceMethod = configClass.getMethod("getInstance",
												new Class[0]);
								if (getInstanceMethod != null &&
										Modifier.isStatic(getInstanceMethod.getModifiers())) {
										instance = (T) getInstanceMethod.invoke(null, new Object[0]);
								}
						}
						catch (NoSuchMethodException exception) {
						}
						catch (InvocationTargetException exception) {
						}
						catch (IllegalAccessException exception) {
						}
						catch (IllegalArgumentException exception) {
						}
						if (instance == null) {
								try {
										instance = configClass.newInstance();
								}
								catch (InstantiationException ex) {
								}
								catch (IllegalAccessException ex) {
								}
								catch (RuntimeException ex) {
								}
						}
						return instance;
				}
		}
}
